<# .SYNOPSIS Configure Detailed Tracking Audit Policies with UI Visibility - Updates both auditpol and audit.csv database. .SCRIPTTYPE Computer Configuration .DESCRIPTION This script applies Detailed Tracking audit policy settings by: 1. Setting active audit policy via auditpol.exe (makes policies active) 2. Updating Group Policy audit.csv database (makes policies visible in secpol.msc UI) The script supports simplified audit policy configuration using comprehensive logging, validation, and error handling. INPUT FORMAT: "1" → Success audit only (logs successful events) "0" → Failure audit only (logs failed events) "1,0" → Both success and failure auditing (logs all events) "" → No auditing (skip this policy) AUDIT POLICIES (6 policies in order): 1. Audit Process Creation - Tracks process startup and command line parameters 2. Audit Process Termination - Monitors process shutdown and exit events 3. Audit DPAPI Activity - Captures Data Protection API operations 4. Audit RPC Events - Tracks Remote Procedure Call operations 5. Audit Plug and Play Events - Monitors hardware device installation and removal 6. Audit Token Right Adjusted Events - Tracks security token privilege adjustments SECURITY RECOMMENDATIONS: - Process Creation: Enable success for comprehensive process monitoring - Process Termination: Enable success for process lifecycle tracking - DPAPI Activity: Enable both for data protection monitoring - RPC Events: Enable failure for RPC security monitoring - Plug and Play Events: Enable success for hardware change tracking - Token Right Adjusted Events: Enable success for privilege escalation detection .PARAMETER PolicyValues JSON array string containing 6 audit values (in order). Use "1" for success, "0" for failure, "1,0" for both, or "" to skip. Format: '["value1","value2","value3","value4","value5","value6"]' .PARAMETER LogLevel Set logging verbosity: Silent, Normal, Verbose, Debug .PARAMETER LogPath Path for log file output. If not specified, auto-configures to agent logs directory. .PARAMETER WhatIf Preview changes without applying them. .EXAMPLE .\Set-DetailedTrackingAuditPolicies.ps1 '["1","1","","1,0","","1"]' Configures process creation (success), termination (success), skips DPAPI, RPC (both), skips PnP, token rights (success) .EXAMPLE .\Set-DetailedTrackingAuditPolicies.ps1 '["1","1","1","0","1","1"]' -WhatIf Preview mode for all 6 policies .NOTES - Requires administrative privileges (Run as Administrator) - Updates C:\Windows\System32\GroupPolicy\Machine\Microsoft\Windows NT\Audit\audit.csv - Policies will be visible in secpol.msc → Advanced Audit Policy Configuration - WARNING: Domain GPO may overwrite these settings on domain-joined systems #> param( [Parameter(Position=0, ValueFromRemainingArguments=$true)] [string[]]$PolicyValuesArray = @("[]"), [ValidateSet('Silent','Normal','Verbose','Debug')] [string]$LogLevel = 'Normal', [string]$LogPath = $null, [switch]$WhatIf ) # Combine all arguments into a single PolicyValues string # First, try to get the original command line with proper quotes $PolicyValues = $null try { $currentPID = $PID Write-Host "Current Process ID: $currentPID" -ForegroundColor Cyan $process = Get-CimInstance Win32_Process -Filter "ProcessId = $currentPID" if ($process) { $commandLine = $process.CommandLine Write-Host "Full command line: $commandLine" -ForegroundColor Yellow if ($commandLine) { # Get the script name for more precise regex matching $scriptName = [System.IO.Path]::GetFileName($MyInvocation.MyCommand.Path) $escapedScriptName = [regex]::Escape($scriptName) # Extract the first argument after this specific script (with all quotes intact) # Stop at known parameters: -LogLevel, -LogPath, -WhatIf, or end of string $pattern = "-File\s+`"[^`"]*\\$escapedScriptName`"\s+(.+?)(?:\s+(?:-LogLevel|-LogPath|-WhatIf)|$)" Write-Host "Using regex pattern: $pattern" -ForegroundColor DarkGray if ($commandLine -match $pattern) { $rawArgument = $matches[1].Trim() Write-Host "Raw argument extracted: $rawArgument" -ForegroundColor Magenta # Remove outer quotes if present if ($rawArgument -match '^"(.*)"$') { $PolicyValues = $matches[1] } else { $PolicyValues = $rawArgument } Write-Host "Extracted PolicyValues from command line: $PolicyValues" -ForegroundColor Green } else { Write-Host "Command line regex did not match. Command line: $commandLine" -ForegroundColor Red } } else { Write-Host "CommandLine property is null or empty" -ForegroundColor Red } } else { Write-Host "Failed to get process information for PID $currentPID" -ForegroundColor Red } } catch { Write-Host "Error extracting from command line: $($_.Exception.Message)" -ForegroundColor Red Write-Verbose "Could not extract from command line: $($_.Exception.Message)" } # Fallback: Use parameter-based approach if command line extraction failed if (-not $PolicyValues) { $PolicyValues = if ($PolicyValuesArray.Count -gt 1) { # Multiple arguments - join them back together $PolicyValuesArray -join '' } else { # Single argument - use as-is $PolicyValuesArray[0] } Write-Verbose "Using parameter-based PolicyValues: $PolicyValues" } # Detailed Tracking Policy Database $PolicyDatabase = @( @{ Name = "Audit Process Creation"; KeyGroup = "Audit"; Key = "Process Creation" }, @{ Name = "Audit Process Termination"; KeyGroup = "Audit"; Key = "Process Termination" }, @{ Name = "Audit DPAPI Activity"; KeyGroup = "Audit"; Key = "DPAPI Activity" }, @{ Name = "Audit RPC Events"; KeyGroup = "Audit"; Key = "RPC Events" }, @{ Name = "Audit Plug and Play Events"; KeyGroup = "Audit"; Key = "Plug and Play Events" }, @{ Name = "Audit Token Right Adjusted Events"; KeyGroup = "Audit"; Key = "Token Right Adjusted Events" } ) # Script-wide variables $script:LogFile = $null $script:StartTime = Get-Date $script:ProcessedCount = 0 $script:SuccessCount = 0 $script:FailureCount = 0 $script:SkippedCount = 0 # Initialize logging function Initialize-LogPath { if ($LogPath) { $logDir = Split-Path $LogPath -Parent if ($logDir -and -not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null } return $LogPath } # Try to get agent directory, fallback to script directory $baseDir = $PSScriptRoot try { $registryPath = if ([Environment]::Is64BitOperatingSystem) { "HKLM:\SOFTWARE\WOW6432Node\AdventNet\DesktopCentral\DCAgent" } else { "HKLM:\SOFTWARE\AdventNet\DesktopCentral\DCAgent" } $agentDir = Get-ItemProperty -Path $registryPath -Name "DCAgentInstallDir" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty DCAgentInstallDir if ($agentDir -and (Test-Path $agentDir)) { $baseDir = $agentDir } } catch { Write-Verbose "Using script directory for logs" } # Create log directory and file path $auditDir = Join-Path (Join-Path $baseDir "logs") "SecurityPolicies" if (-not (Test-Path $auditDir)) { New-Item -ItemType Directory -Path $auditDir -Force | Out-Null } $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $scriptName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.ScriptName) if ([string]::IsNullOrEmpty($scriptName)) { $scriptName = "Set-DetailedTrackingAuditPolicies" } return Join-Path $auditDir "${scriptName}_$timestamp.log" } $script:LogFile = try { Initialize-LogPath } catch { $null } # Logging Functions function Write-Log { param( [Parameter(Mandatory=$true)] [string]$Message, [ValidateSet('Info','Warning','Error','Debug','Success')] [string]$Level = 'Info', [string]$Component = 'DetailedTrackingAudit' ) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $logMessage = "[$timestamp] [$Level] [$Component] $Message" # Console output based on level and LogLevel setting switch ($Level) { 'Error' { if ($LogLevel -ne 'Silent') { Write-Error $Message } } 'Warning' { if ($LogLevel -notin @('Silent')) { Write-Warning $Message } } 'Success' { if ($LogLevel -notin @('Silent')) { Write-Host $Message -ForegroundColor Green } } 'Debug' { if ($LogLevel -eq 'Debug') { Write-Host $Message -ForegroundColor Gray } } 'Info' { if ($LogLevel -notin @('Silent')) { Write-Host $Message } } } # File output if LogPath is specified if ($script:LogFile) { Add-Content -Path $script:LogFile -Value $logMessage -Encoding UTF8 } } function Write-ProgressLog { param( [int]$Current, [int]$Total, [string]$Activity = "Processing Detailed Tracking Audit Policies", [string]$CurrentItem = "" ) if ($LogLevel -ne 'Silent') { $percentComplete = if ($Total -gt 0) { ($Current / $Total) * 100 } else { 0 } Write-Progress -Activity $Activity -Status "Processing $Current of $Total - $CurrentItem" -PercentComplete $percentComplete } } function Test-Admin { $id = [Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object Security.Principal.WindowsPrincipal($id) return $p.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) } # Update the Group Policy audit database (audit.csv) so settings show in secpol.msc # Update the Group Policy audit database (audit.csv) so settings show in secpol.msc function Update-AuditDatabase { param( [Parameter(Mandatory=$true)] [string]$Subcategory, [Parameter(Mandatory=$true)] [string]$PolicyName, [Parameter(Mandatory=$false)] [ValidateSet('enable', 'disable')] [string]$Success, [Parameter(Mandatory=$false)] [ValidateSet('enable', 'disable')] [string]$Failure ) $auditDbPath = "$env:SystemRoot\System32\GroupPolicy\Machine\Microsoft\Windows NT\Audit" $auditCsvPath = Join-Path $auditDbPath "audit.csv" # Create directory if it doesn't exist if (-not (Test-Path $auditDbPath)) { Write-Log "Creating audit database directory: $auditDbPath" -Level Info -Component "AuditDB" if (-not $WhatIf) { New-Item -Path $auditDbPath -ItemType Directory -Force | Out-Null } } # Get GUID from auditpol for this subcategory $subcategoryGuid = "" try { $auditpolOutput = & auditpol /list /subcategory:* /r 2>$null if ($auditpolOutput) { # Parse CSV output and find matching subcategory $guidLine = $auditpolOutput | Select-String -Pattern "^\s*$Subcategory," if ($guidLine) { # Extract GUID from the line (format: "Subcategory Name,{GUID}") if ($guidLine.Line -match '\{([0-9A-F-]+)\}') { $subcategoryGuid = $matches[0] # Include braces Write-Log "Retrieved GUID for '$Subcategory': $subcategoryGuid" -Level Debug -Component "AuditDB" } } } } catch { Write-Log "Could not retrieve GUID for '$Subcategory': $($_.Exception.Message)" -Level Debug -Component "AuditDB" } # Initialize or read existing audit.csv $csvHeader = "Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value" $auditEntries = [System.Collections.ArrayList]@() $hasHeader = $false $foundEntry = $false # Track if we found entry for this subcategory $existingGuid = $subcategoryGuid # Use the GUID we just retrieved $existingSubcategoryName = "" # Store the subcategory name with prefix if (Test-Path $auditCsvPath) { Write-Log "Reading existing audit database: $auditCsvPath" -Level Debug -Component "AuditDB" # Read with UTF8 encoding $csvLines = [System.IO.File]::ReadAllLines($auditCsvPath, [System.Text.UTF8Encoding]::new($true)) foreach ($line in $csvLines) { # Skip header line if ($line -match '^Machine Name,') { $hasHeader = $true continue } # Skip empty lines if ([string]::IsNullOrWhiteSpace($line)) { continue } # Check if this line is a policy entry (starts with comma for Machine Name field) if ($line -match '^,([^,]*),([^,]+),([^,]*)') { $lineSubcategory = $matches[2].Trim() $lineGuid = $matches[3].Trim() # Check if this is our subcategory (with or without "Audit" prefix) $isOurSubcategory = ($lineSubcategory -eq $Subcategory) -or ($lineSubcategory -eq "Audit $Subcategory") if ($isOurSubcategory) { # Found our subcategory - save GUID if it exists and skip (we'll add updated version) $foundEntry = $true if (-not [string]::IsNullOrEmpty($lineGuid)) { $existingGuid = $lineGuid } $existingSubcategoryName = $lineSubcategory Write-Log "Found existing entry for: $lineSubcategory (GUID: $lineGuid) - will update it" -Level Debug -Component "AuditDB" continue } else { # Preserve other policy entries Write-Log "Preserving existing entry for: $lineSubcategory" -Level Debug -Component "AuditDB" $auditEntries.Add($line) | Out-Null } } else { # Preserve non-policy lines (like Option: entries) $auditEntries.Add($line) | Out-Null } } } else { Write-Log "Audit database does not exist, will create: $auditCsvPath" -Level Info -Component "AuditDB" } # Build audit setting value (0=None, 1=Success, 2=Failure, 3=Both) # and inclusion setting text $auditValue = 0 $inclusionSetting = "No Auditing" if ($Success -eq 'enable' -and $Failure -eq 'enable') { $auditValue = 3 # Both $inclusionSetting = "Success and Failure" $valueDesc = "Success and Failure" } elseif ($Success -eq 'enable') { $auditValue = 1 # Success only $inclusionSetting = "Success" $valueDesc = "Success" } elseif ($Failure -eq 'enable') { $auditValue = 2 # Failure only $inclusionSetting = "Failure" $valueDesc = "Failure" } else { $inclusionSetting = "No Auditing" $valueDesc = "No Auditing" } # Determine the subcategory name to use (with "Audit " prefix) # If existing entry had a name, use it; otherwise add "Audit " prefix if ($foundEntry -and $existingSubcategoryName) { $csvSubcategoryName = $existingSubcategoryName } else { $csvSubcategoryName = "Audit $Subcategory" } # Format: Machine Name,Policy Target,Subcategory,Subcategory GUID,Inclusion Setting,Exclusion Setting,Setting Value # Example: ,System,Audit Security State Change,{0cce9210-69ae-11d9-bed3-505054503030},Success,,1 # Create the entry with GUID $updatedEntry = ",System,$csvSubcategoryName,$existingGuid,$inclusionSetting,,$auditValue" $auditEntries.Add($updatedEntry) | Out-Null Write-Log "Added entry for '$csvSubcategoryName' with GUID ${existingGuid}: $valueDesc (Value=$auditValue)" -Level Info -Component "AuditDB" if (-not $WhatIf) { # Write updated audit.csv with header $outputLines = @() $outputLines += $csvHeader $outputLines += $auditEntries # Use UTF8 with BOM for better compatibility $utf8BOM = New-Object System.Text.UTF8Encoding $true [System.IO.File]::WriteAllLines($auditCsvPath, $outputLines, $utf8BOM) Write-Log "Audit database updated successfully" -Level Success -Component "AuditDB" Write-Log "Policy '$PolicyName' will now show in secpol.msc UI" -Level Success -Component "AuditDB" } else { Write-Log "WHATIF: Would update audit database for '$Subcategory' with value $auditValue ($inclusionSetting)" -Level Info -Component "AuditDB" } } function Initialize-Script { Write-Log "========== Detailed Tracking Audit Policy Framework Script Started ==========" Write-Log "Script: $($MyInvocation.MyCommand.Name)" Write-Log "User: $env:USERNAME" Write-Log "Computer: $env:COMPUTERNAME" Write-Log "PowerShell Version: $($PSVersionTable.PSVersion)" Write-Log "Log Level: $LogLevel" if ($script:LogFile) { Write-Log "Log File: $($script:LogFile)" } else { Write-Log "Logging: Console only" } if ($WhatIf) { Write-Log "WhatIf mode enabled - no changes will be applied" -Level Warning } if (-not (Test-Admin)) { Write-Log "Administrator privileges required. Please run this script as Administrator." -Level Error throw "Administrator privileges required" } Write-Log "Administrator check passed" -Level Success # Validate PolicyDatabase if (-not $PolicyDatabase -or $PolicyDatabase.Count -eq 0) { Write-Log "PolicyDatabase is empty. Please configure audit policies before running." -Level Warning return $false } # Validate all policies are audit type $nonAuditPolicies = $PolicyDatabase | Where-Object { $_.KeyGroup -ne "Audit" } if ($nonAuditPolicies) { Write-Log "Found $($nonAuditPolicies.Count) non-audit policies in database. This script only handles audit policies." -Level Error return $false } # Validate required properties foreach ($policy in $PolicyDatabase) { $hasName = $policy.ContainsKey('Name') -or ($policy.PSObject.Properties.Name -contains 'Name') $hasKeyGroup = $policy.ContainsKey('KeyGroup') -or ($policy.PSObject.Properties.Name -contains 'KeyGroup') $hasKey = $policy.ContainsKey('Key') -or ($policy.PSObject.Properties.Name -contains 'Key') if (-not ($hasName -and $hasKeyGroup -and $hasKey)) { $missingProps = @() if (-not $hasName) { $missingProps += 'Name' } if (-not $hasKeyGroup) { $missingProps += 'KeyGroup' } if (-not $hasKey) { $missingProps += 'Key' } Write-Log "Policy '$($policy.Name)' missing required properties: $($missingProps -join ', ')" -Level Error return $false } } Write-Log "PolicyDatabase validation passed - contains $($PolicyDatabase.Count) Detailed Tracking audit policies" -Level Success return $true } if (-not (Initialize-Script)) { return } # Audit Policy Functions function Set-AuditPolicy { param( [Parameter(Mandatory=$true)] [string]$PolicyName, [Parameter(Mandatory=$true)] [string]$Subcategory, [Parameter(Mandatory=$false)] [ValidateSet('enable', 'disable')] [string]$Success, [Parameter(Mandatory=$false)] [ValidateSet('enable', 'disable')] [string]$Failure ) Write-Log "Setting audit policy: $PolicyName (Subcategory: $Subcategory)" -Level Debug -Component "Audit" if ([string]::IsNullOrWhiteSpace($Subcategory)) { throw "Subcategory name is required for audit policy: $PolicyName" } $auditArgs = @('/set', "/subcategory:`"$Subcategory`"") if ($Success) { $auditArgs += "/success:$Success" Write-Log "Success auditing: $Success" -Level Debug -Component "Audit" } if ($Failure) { $auditArgs += "/failure:$Failure" Write-Log "Failure auditing: $Failure" -Level Debug -Component "Audit" } if ($auditArgs.Count -le 2) { throw "At least one of Success or Failure auditing must be specified for policy: $PolicyName" } $display = "auditpol.exe " + ($auditArgs -join ' ') Write-Log "Audit command: $display" -Level Debug -Component "Audit" if (-not $WhatIf) { Write-Log "Executing: $display" -Level Info -Component "Audit" $p = Start-Process -FilePath 'auditpol.exe' -ArgumentList $auditArgs -NoNewWindow -Wait -PassThru if ($p.ExitCode -ne 0) { Write-Log "auditpol failed (exit $($p.ExitCode)) for subcategory '$Subcategory'" -Level Error -Component "Audit" throw "auditpol failed (exit $($p.ExitCode)) for subcategory '$Subcategory'" } else { $auditSettings = @() if ($Success) { $auditSettings += "Success:$Success" } if ($Failure) { $auditSettings += "Failure:$Failure" } $settingsText = $auditSettings -join ", " Write-Log "Successfully applied audit policy for '$Subcategory' ($settingsText)" -Level Success -Component "Audit" } } else { Write-Log "WHATIF: Would execute: $display" -Level Info -Component "Audit" } } function Invoke-AuditPolicy { param( [Parameter(Mandatory=$true)] [pscustomobject]$Policy, [AllowEmptyString()] [string]$Argument = "" ) Write-Log "Processing audit policy: '$($Policy.Name)' with argument: '$Argument'" -Level Debug -Component "Audit" # Handle simplified input format # Empty/null argument means disable both Success and Failure auditing $successAudit = $null $failureAudit = $null if ([string]::IsNullOrEmpty($Argument)) { # Disable both success and failure auditing $successAudit = 'disable' $failureAudit = 'disable' Write-Log "Configuring NO AUDITING for '$($Policy.Name)' (disabling both Success and Failure)" -Level Info -Component "Audit" } else { switch ($Argument) { '1' { # Success only - explicitly disable failure auditing $successAudit = 'enable' $failureAudit = 'disable' Write-Log "Configuring SUCCESS auditing for '$($Policy.Name)' (disabling Failure)" -Level Info -Component "Audit" } '0' { # Failure only - explicitly disable success auditing $successAudit = 'disable' $failureAudit = 'enable' Write-Log "Configuring FAILURE auditing for '$($Policy.Name)' (disabling Success)" -Level Info -Component "Audit" } '1,0' { # Both success and failure $successAudit = 'enable' $failureAudit = 'enable' Write-Log "Configuring SUCCESS AND FAILURE auditing for '$($Policy.Name)'" -Level Info -Component "Audit" } '0,1' { # Both success and failure $successAudit = 'enable' $failureAudit = 'enable' Write-Log "Configuring SUCCESS AND FAILURE auditing for '$($Policy.Name)'" -Level Info -Component "Audit" } default { Write-Log "Invalid audit format for '$($Policy.Name)': '$Argument'. Expected '1', '0', '1,0', or ''. Skipping..." -Level Warning -Component "Audit" return } } } # Step 1: Set active audit policy via auditpol $setAuditParams = @{ PolicyName = $Policy.Name Subcategory = $Policy.Key } if ($successAudit) { $setAuditParams.Success = $successAudit } if ($failureAudit) { $setAuditParams.Failure = $failureAudit } Set-AuditPolicy @setAuditParams # Step 2: Update audit database so policy shows in secpol.msc UI $updateDbParams = @{ Subcategory = $Policy.Key PolicyName = $Policy.Name } if ($successAudit) { $updateDbParams.Success = $successAudit } if ($failureAudit) { $updateDbParams.Failure = $failureAudit } Update-AuditDatabase @updateDbParams } # Main processing loop with comprehensive logging Write-Log "Starting Detailed Tracking audit policy processing for $($PolicyDatabase.Count) policies" # Parse PolicyValues array string to array function Parse-PolicyValuesArray { param([string]$ArrayString) # Check if input is JSON format (starts with [ and ends with ]) if ($ArrayString -match '^\s*\[.*\]\s*$') { Write-Log "Detected JSON format input, attempting to parse..." -Level Debug try { # Parse the JSON array string $Arguments = ConvertFrom-Json $ArrayString Write-Log "Successfully parsed JSON policy values array: $($Arguments.Count) values provided" -Level Info return $Arguments } catch { Write-Log "Failed to parse as JSON: $($_.Exception.Message)" -Level Warning Write-Log "Falling back to positional argument parsing..." -Level Info } } else { Write-Log "Input is not in JSON format (doesn't start with [ ), using as positional arguments" -Level Info } # Fallback: Use PolicyValuesArray as positional arguments Write-Log "Using $($PolicyValuesArray.Count) positional arguments" -Level Info return $PolicyValuesArray } Write-Log "Arguments provided: $PolicyValues" # Parse PolicyValues array string to get individual arguments $Arguments = Parse-PolicyValuesArray -ArrayString $PolicyValues Write-Log "Arguments provided: $($Arguments.Count)" for ($i = 0; $i -lt $PolicyDatabase.Count; $i++) { $script:ProcessedCount++ $policy = $PolicyDatabase[$i] Write-ProgressLog -Current ($i + 1) -Total $PolicyDatabase.Count -CurrentItem $policy.Name # Use empty string if no argument provided (will disable both Success and Failure) $arg = if ($i -ge $Arguments.Count) { "" } else { ([string]$Arguments[$i]).Trim() } Write-Log "Processing policy #$($i+1): '$($policy.Name)' with argument: '$arg'" -Level Info # Show policy details if ($LogLevel -eq 'Debug') { Write-Log "Policy Details - Name: $($policy.Name), KeyGroup: $($policy.KeyGroup), Key: $($policy.Key), Argument: $arg" -Level Debug } try { Invoke-AuditPolicy -Policy $policy -Argument $arg $script:SuccessCount++ Write-Log "Successfully processed audit policy: '$($policy.Name)'" -Level Success } catch { $script:FailureCount++ Write-Log "Failed to process audit policy '$($policy.Name)': $($_.Exception.Message)" -Level Error # Continue processing other policies unless it's a critical error if ($_.Exception.Message -match "Administrator|Permission|Access|auditpol") { Write-Log "Critical audit error encountered. Check permissions and auditpol availability." -Level Error # Don't break - continue with other policies } } Write-Log "--- Policy #$($i+1) completed ---" -Level Debug } # Final summary and cleanup function Write-CompletionSummary { $endTime = Get-Date $duration = $endTime - $script:StartTime Write-Log "========== Detailed Tracking Audit Policy Framework Execution Summary ==========" -Level Info Write-Log "Execution Duration: $($duration.ToString('hh\:mm\:ss'))" -Level Info Write-Log "Total Policies in Database: $($PolicyDatabase.Count)" -Level Info Write-Log "Successfully Applied: $script:SuccessCount" -Level Success Write-Log "Failed: $script:FailureCount" -Level $(if ($script:FailureCount -gt 0) { 'Warning' } else { 'Info' }) Write-Log "Not Configured: $script:SkippedCount" -Level Info $actuallyProcessed = $script:SuccessCount + $script:FailureCount if ($actuallyProcessed -gt 0) { $successRate = [math]::Round(($script:SuccessCount / $actuallyProcessed) * 100, 2) Write-Log "Success Rate: $successRate% (of actually processed policies)" -Level Info } if ($script:FailureCount -gt 0) { Write-Log "Some Detailed Tracking audit policies failed to apply. Check the log for details." -Level Warning Write-Log "Common issues: Invalid subcategory names, insufficient permissions, auditpol not available" -Level Warning } if ($script:SuccessCount -gt 0) { Write-Log " " -Level Info Write-Log "VERIFICATION STEPS:" -Level Info Write-Log "1. Run: auditpol /get /category:'Detailed Tracking'" -Level Info Write-Log "2. Open: secpol.msc → Advanced Audit Policy Configuration → System Audit Policies → Detailed Tracking" -Level Info Write-Log "3. Policies should be visible in both locations" -Level Info } if ($WhatIf) { Write-Log "WhatIf mode was enabled - no actual changes were made." -Level Info } if ($script:LogFile) { Write-Log "Detailed log saved to: $script:LogFile" -Level Info } Write-Log "========== Detailed Tracking Audit Policy Framework Execution Complete ==========" -Level Info } Write-CompletionSummary # Return exit code based on results if ($script:FailureCount -gt 0) { exit 1 } else { exit 0 }